/**
 * Copyright (c) 2021-Now http://faasadmin.com All rights reserved.
 * No deletion without permission, or be held responsible to law.
 */

package com.faasadmin.faas.modules.admin.admin.service.codegen.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.faasadmin.faas.addonss.admin.admin.service.codegen.ToolCodegenService;
import com.faasadmin.faas.modules.admin.admin.service.codegen.inner.CodegenBuilder;
import com.faasadmin.faas.modules.admin.admin.service.codegen.inner.CodegenEngine;
import com.faasadmin.faas.services.system.dal.dataobject.user.SysUserDO;
import com.faasadmin.faas.business.core.module.system.service.user.SysUserBussService;
import com.faasadmin.faas.services.tool.convert.codegen.ToolCodegenConvert;
import com.faasadmin.faas.services.tool.dal.dataobject.codegen.ToolCodegenColumnDO;
import com.faasadmin.faas.services.tool.dal.dataobject.codegen.ToolCodegenTableDO;
import com.faasadmin.faas.services.tool.dal.mysql.codegen.ToolCodegenColumnMapper;
import com.faasadmin.faas.services.tool.dal.mysql.codegen.ToolCodegenTableMapper;
import com.faasadmin.faas.services.tool.service.db.ToolDatabaseTableService;
import com.faasadmin.faas.services.tool.vo.codegen.ToolCodegenBatchUpdateReqVO;
import com.faasadmin.faas.services.tool.vo.codegen.ToolCodegenCreateListReqVO;
import com.faasadmin.faas.services.tool.vo.codegen.ToolCodegenUpdateReqVO;
import com.faasadmin.faas.services.tool.vo.codegen.table.ToolCodegenTablePageReqVO;
import com.faasadmin.faas.services.tool.vo.codegen.table.ToolDatabaseTableRespVO;
import com.faasadmin.framework.common.enums.codegen.CodeBatchUpdateFieldEnum;
import com.faasadmin.framework.common.enums.codegen.CodegenSceneEnum;
import com.faasadmin.framework.common.pojo.PageResult;
import com.faasadmin.framework.common.utils.StrUtils;
import com.faasadmin.framework.common.utils.collection.CollectionUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.faasadmin.framework.common.constant.ErrorCodeConstants.*;
import static com.faasadmin.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * 代码生成 Service 实现类
 *
 * @author faasadmin
 */
@Service
public class ToolCodegenServiceImpl implements ToolCodegenService {

    @Resource
    private ToolCodegenTableMapper codegenTableMapper;
    @Resource
    private ToolCodegenColumnMapper codegenColumnMapper;
    @Resource
    private ToolDatabaseTableService databaseTableService;
    @Resource
    private CodegenBuilder codegenBuilder;
    @Resource
    private CodegenEngine codegenEngine;
    @Resource
    private SysUserBussService sysUserService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Long> createCodegenList(Long userId, ToolCodegenCreateListReqVO reqVO) {
        List<Long> ids = new ArrayList<>(reqVO.getTableNames().size());
        // 遍历添加。虽然效率会低一点，但是没必要做成完全批量，因为不会这么大量
        reqVO.getTableNames().forEach(tableName -> ids.add(createCodegen(userId, reqVO.getDataSourceConfigId(), tableName)));
        return ids;
    }

    public Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) {
        // 从数据库中，获得数据库表结构
        com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, tableName);
        // 导入
        return createCodegen0(userId, dataSourceConfigId, tableInfo);
    }

    private Long createCodegen0(Long userId, Long dataSourceConfigId, TableInfo tableInfo) {
        // 校验导入的表和字段非空
        checkTableInfo(tableInfo);
        // 校验是否已经存在
        if (codegenTableMapper.selectByTableNameAndDataSourceConfigId(tableInfo.getName(), dataSourceConfigId) != null) {
            throw exception(CODEGEN_TABLE_EXISTS);
        }
        SysUserDO sysUserDO = sysUserService.getUser(userId);
        // 构建 ToolCodegenTableDO 对象，插入到 DB 中
        ToolCodegenTableDO table = codegenBuilder.buildTable(tableInfo);
        table.setDataSourceConfigId(dataSourceConfigId);
        table.setScene(CodegenSceneEnum.ADMIN.getScene()); // 默认配置下，使用管理后台的模板
        table.setAuthor(sysUserDO.getNickname());
        codegenTableMapper.insert(table);
        // 构建 ToolCodegenColumnDO 数组，插入到 DB 中
        List<ToolCodegenColumnDO> columns = codegenBuilder.buildColumns(table.getId(), tableInfo.getFields());
        // 如果没有主键，则使用第一个字段作为主键
        if (!tableInfo.isHavePrimaryKey()) {
            columns.get(0).setPrimaryKey(true);
        }
        codegenColumnMapper.insertBatch(columns);
        return table.getId();
    }

    private void checkTableInfo(com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) {
        if (tableInfo == null) {
            throw exception(CODEGEN_IMPORT_TABLE_NULL);
        }
        if (StrUtil.isEmpty(tableInfo.getComment())) {
            throw exception(CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
        }
        if (CollUtil.isEmpty(tableInfo.getFields())) {
            throw exception(CODEGEN_IMPORT_COLUMNS_NULL);
        }
        tableInfo.getFields().forEach(field -> {
            if (StrUtil.isEmpty(field.getComment())) {
                throw exception(CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
            }
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCodegen(ToolCodegenUpdateReqVO updateReqVO) {
        // 校验是否已经存在
        if (codegenTableMapper.selectById(updateReqVO.getTable().getId()) == null) {
            throw exception(CODEGEN_TABLE_NOT_EXISTS);
        }
        // 更新 table 表定义
        ToolCodegenTableDO updateTableObj = ToolCodegenConvert.INSTANCE.convert(updateReqVO.getTable());
        codegenTableMapper.updateById(updateTableObj);
        // 更新 column 字段定义
        List<ToolCodegenColumnDO> updateColumnObjs = ToolCodegenConvert.INSTANCE.convertList03(updateReqVO.getColumns());
        updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj));
    }

    @Override
    public void batchUpdateCodegen(ToolCodegenBatchUpdateReqVO updateReqVO) {
        String field = updateReqVO.getField();
        List<Long> ids = updateReqVO.getIds();
        switch (CodeBatchUpdateFieldEnum.valueOfKey(field)) {
            case SCENE:
                for (Long id : ids) {
                    UpdateWrapper<ToolCodegenTableDO> updateSceneWrapper = new UpdateWrapper<ToolCodegenTableDO>();
                    updateSceneWrapper.eq("id", id);
                    updateSceneWrapper.set("scene", updateReqVO.getScene());
                    codegenTableMapper.update(null, updateSceneWrapper);
                }
                break;
            case AUTHOR:
                for (Long id : ids) {
                    UpdateWrapper<ToolCodegenTableDO> updateAuthorWrapper = new UpdateWrapper<ToolCodegenTableDO>();
                    updateAuthorWrapper.eq("id", id);
                    updateAuthorWrapper.set("author", updateReqVO.getAuthor());
                    codegenTableMapper.update(null, updateAuthorWrapper);
                }
                break;
            case MODULE_NAME:
                for (Long id : ids) {
                    UpdateWrapper<ToolCodegenTableDO> updateModuleNameWrapper = new UpdateWrapper<ToolCodegenTableDO>();
                    updateModuleNameWrapper.eq("id", id);
                    updateModuleNameWrapper.set("module_name", updateReqVO.getModuleName());
                    codegenTableMapper.update(null, updateModuleNameWrapper);
                }
                break;
            case PARENT_MENU_ID:
                for (Long id : ids) {
                    UpdateWrapper<ToolCodegenTableDO> updateParentMenuIdWrapper = new UpdateWrapper<ToolCodegenTableDO>();
                    updateParentMenuIdWrapper.eq("id", id);
                    updateParentMenuIdWrapper.set("parent_menu_id", updateReqVO.getParentMenuId());
                    codegenTableMapper.update(null, updateParentMenuIdWrapper);
                }
                break;
            case BUSINESS_NAME:
                String oldBusinessName = updateReqVO.getOldName();
                String newBusinessName = updateReqVO.getNewName();
                List<ToolCodegenTableDO> codegenTables = getCodegenTableList(ids);
                for (ToolCodegenTableDO codegenTable : codegenTables) {
                    String replace = StrUtils.replace(codegenTable.getBusinessName(), oldBusinessName, newBusinessName);
                    UpdateWrapper<ToolCodegenTableDO> updateParentMenuIdWrapper = new UpdateWrapper<ToolCodegenTableDO>();
                    updateParentMenuIdWrapper.eq("id", codegenTable.getId());
                    updateParentMenuIdWrapper.set("business_name", replace);
                    codegenTableMapper.update(null, updateParentMenuIdWrapper);
                }
                break;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncCodegenFromDB(Long tableId) {
        // 校验是否已经存在
        ToolCodegenTableDO table = codegenTableMapper.selectById(tableId);
        if (table == null) {
            throw exception(CODEGEN_TABLE_NOT_EXISTS);
        }
        // 从数据库中，获得数据库表结构
        com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo = databaseTableService.getTable(table.getDataSourceConfigId(), table.getTableName());
        // 执行同步
        syncCodegen0(tableId, tableInfo);
    }

    private void syncCodegen0(Long tableId, com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) {
        // 校验导入的表和字段非空
        checkTableInfo(tableInfo);
        List<TableField> tableFields = tableInfo.getFields();
        // 构建 ToolCodegenColumnDO 数组，只同步新增的字段
        List<ToolCodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
        Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, ToolCodegenColumnDO::getColumnName);
        // 计算需要删除的字段
        Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
        Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !tableFieldNames.contains(column.getColumnName())).map(ToolCodegenColumnDO::getId).collect(Collectors.toSet());
        // 移除已经存在的字段
        tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
        if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
            throw exception(CODEGEN_SYNC_NONE_CHANGE);
        }
        // 插入新增的字段
        List<ToolCodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
        codegenColumnMapper.insertBatch(columns);
        // 删除不存在的字段
        if (CollUtil.isNotEmpty(deleteColumnIds)) {
            codegenColumnMapper.deleteBatchIds(deleteColumnIds);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCodegen(Long tableId) {
        // 校验是否已经存在
        if (codegenTableMapper.selectById(tableId) == null) {
            throw exception(CODEGEN_TABLE_NOT_EXISTS);
        }
        // 删除 table 表定义
        codegenTableMapper.deleteById(tableId);
        // 删除 column 字段定义
        codegenColumnMapper.deleteListByTableId(tableId);
    }

    @Override
    public void batchDeleteCodegen(List<Long> tableIdS) {
        for (Long tableId : tableIdS) {
            deleteCodegen(tableId);
        }
    }

    @Override
    public PageResult<ToolCodegenTableDO> getCodegenTablePage(ToolCodegenTablePageReqVO pageReqVO) {
        return codegenTableMapper.selectPage(pageReqVO);
    }

    @Override
    public ToolCodegenTableDO getCodegenTablePage(Long id) {
        return codegenTableMapper.selectById(id);
    }

    @Override
    public List<ToolCodegenColumnDO> getCodegenColumnListByTableId(Long tableId) {
        return codegenColumnMapper.selectListByTableId(tableId);
    }

    @Override
    public Map<String, String> generationCodes(Long tableId) {
        // 校验是否已经存在
        ToolCodegenTableDO table = codegenTableMapper.selectById(tableId);
        if (table == null) {
            throw exception(CODEGEN_TABLE_NOT_EXISTS);
        }
        List<ToolCodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(tableId);
        if (CollUtil.isEmpty(columns)) {
            throw exception(CODEGEN_COLUMN_NOT_EXISTS);
        }
        // 执行生成
        Map<String, String> resutl = codegenEngine.execute(table, columns);
        //codegenEngine.handleErrorCode(Lists.newArrayList(table), resutl);
        return resutl;
    }

    @Override
    public byte[] batchGenerationCodes(List<String> tableIds) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Map<String, String> stringStringMap = Maps.newHashMap();
        List<ToolCodegenTableDO> toolCodegenTableDOS = Lists.newArrayList();
        for (String tableId : tableIds) {
            Long tid = Convert.toLong(tableId);
            // 校验是否已经存在
            ToolCodegenTableDO table = codegenTableMapper.selectById(tid);
            if (table == null) {
                throw exception(CODEGEN_TABLE_NOT_EXISTS);
            }
            toolCodegenTableDOS.add(table);
            List<ToolCodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(tid);
            if (CollUtil.isEmpty(columns)) {
                throw exception(CODEGEN_COLUMN_NOT_EXISTS);
            }
            // 执行生成
            stringStringMap.putAll(codegenEngine.execute(table, columns));
        }
        //codegenEngine.handleErrorCode(toolCodegenTableDOS, stringStringMap);
        String[] paths = stringStringMap.keySet().toArray(new String[0]);
        // 构建 zip 包
        ByteArrayInputStream[] ins = stringStringMap.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
        ZipUtil.zip(outputStream, paths, ins);
        // 输出
        return outputStream.toByteArray();
    }

    @Override
    public List<ToolDatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {
        List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);
        // 移除置顶前缀的表名
        tables.removeIf(table -> table.getName().toUpperCase().startsWith("QRTZ_"));
        tables.removeIf(table -> table.getName().toUpperCase().startsWith("ACT_"));
        tables.removeIf(table -> table.getName().toUpperCase().startsWith("FLW_"));
        // 移除已经生成的表
        // 移除在 Codegen 中，已经存在的
        Set<String> existsTables = CollectionUtils.convertSet(codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId), ToolCodegenTableDO::getTableName);
        tables.removeIf(table -> existsTables.contains(table.getName()));
        return ToolCodegenConvert.INSTANCE.convertList04(tables);
    }

    @Override
    public List<ToolCodegenTableDO> getCodegenTableList(List<Long> tableIds) {
        return codegenTableMapper.selectBatchIds(tableIds);
    }

}
